using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.CSharp;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Globalization;

namespace ESBSimpleSamples.ServiceClient.Generator.Helpers
{
    public static class CodeDomHelper
    {
        private static CSharpCodeProvider _codeProvider;

        private static CSharpCodeProvider CodeProvider
        {
            get
            {
                if (_codeProvider == null)
                {
                    _codeProvider = new CSharpCodeProvider();
                }
                return _codeProvider;
            }
        }

        public static CodeMemberField GetField(string fieldName, Type fieldType)
        {
            return GetField(fieldName, fieldType, null, 0);
        }

        public static CodeMemberField GetField(string fieldName, Type fieldType, MemberAttributes attributes)
        {
            return GetField(fieldName, fieldType, null, attributes);
        }

        public static CodeMemberField GetField(string fieldName, Type fieldType, object initialValue)
        {
            return GetField(fieldName, fieldType, initialValue, 0);
        }

        public static CodeMemberField GetField(string fieldName, Type fieldType, object initialValue, MemberAttributes attributes)
        {
            CodeMemberField field = new CodeMemberField(fieldType, fieldName);
            if (initialValue != null)
            {
                field.InitExpression = new CodePrimitiveExpression(initialValue);
            }
            if (attributes != 0)
            {
                field.Attributes = attributes;
            }
            return field;
        }

        public static CodeMemberProperty GetProperty(string propertyName, string fieldName, Type propertyType)
        {
            return GetProperty(propertyName, fieldName, propertyType, MemberAttributes.Public);
        }

        public static CodeMemberProperty GetProperty(string propertyName, string fieldName, Type propertyType, MemberAttributes attributes)
        {
            return GetProperty(propertyName, fieldName, propertyType, attributes, true);
        }

        public static CodeMemberProperty GetProperty(string propertyName, string fieldName, Type propertyType, MemberAttributes attributes, bool addDefaultAccessors)
        {
            CodeMemberProperty property = new CodeMemberProperty();
            property.Attributes = attributes;
            property.Name = propertyName;
            property.Type = new CodeTypeReference(propertyType);
            if (addDefaultAccessors)
            {
                property.GetStatements.Add(new CodeMethodReturnStatement(new CodeFieldReferenceExpression(null, fieldName)));
                property.SetStatements.Add(new CodeAssignStatement(new CodeFieldReferenceExpression(null, fieldName), new CodePropertySetValueReferenceExpression()));
            }
            return property;
        }

        public static CodeMemberProperty GetProperty(string propertyName, string fieldName, Type propertyType, CodeSnippetExpression getExpression, CodeSnippetExpression setExpression)
        {
            return GetProperty(propertyName, fieldName, propertyType, MemberAttributes.Public, getExpression, setExpression);
        }

        public static CodeMemberProperty GetProperty(string propertyName, string fieldName, Type propertyType, MemberAttributes attributes, CodeSnippetExpression getExpression, CodeSnippetExpression setExpression)
        {
            CodeMemberProperty property = GetProperty(propertyName, fieldName, propertyType, attributes, false);
            property.HasGet = (getExpression != null);
            if (getExpression != null)
            {
                property.GetStatements.Add(getExpression);
            }
            property.HasSet = (setExpression != null);
            if (setExpression != null)
            {
                property.SetStatements.Add(setExpression);
            }
            return property;
        }

        public static CodeMemberProperty GetProperty(string propertyName, Type propertyType, MemberAttributes attributes, CodeStatement getExpression)
        {
            CodeMemberProperty property = GetProperty(propertyName, string.Empty, propertyType, attributes, false);
            property.HasSet = false;
            property.HasGet = (getExpression != null);
            if (getExpression != null)
            {
                property.GetStatements.Add(getExpression);
            }
            return property;
        }

        public static CodeMemberField GetConstant<T>(string name, T value)
        {
            //create string field:
            CodeMemberField constField = new CodeMemberField(typeof(T), GetValidId(name));
            constField.InitExpression = new CodePrimitiveExpression(value);
            //set to public and constant:
            constField.Attributes = (constField.Attributes & ~MemberAttributes.AccessMask & ~MemberAttributes.ScopeMask) | MemberAttributes.Public | MemberAttributes.Const;
            return constField;
        }

        public static CodeCommentStatementCollection GetSummaryComments(string additionalText)
        {
            CodeCommentStatementCollection coll = new CodeCommentStatementCollection();
            coll.Add(new CodeCommentStatement("<summary>", true));
            if (!string.IsNullOrEmpty(additionalText))
            {
                coll.Add(new CodeCommentStatement(additionalText, true));
            }
            coll.Add(new CodeCommentStatement("</summary>", true));
            return coll;
        }

        public static string GetValidId(string idName)
        {
            string result = null;
            if (!CodeProvider.IsValidIdentifier(result))
            {
                //CreateValidIdentifier is a good name, but the method doesn't seem to work:
                //result = CodeProvider.CreateValidIdentifier(idName);
                UnicodeCategory category;
                int ordinal = 0;
                result = idName.Replace(" ", string.Empty).Replace("@", string.Empty); ;
                ordinal = 1;
                while (ordinal < result.Length)
                {
                    category = Char.GetUnicodeCategory(result[ordinal]);
                    if (category != UnicodeCategory.UppercaseLetter &&
                          UnicodeCategory.LowercaseLetter != category &&
                          UnicodeCategory.TitlecaseLetter != category &&
                          UnicodeCategory.ModifierLetter != category &&
                          UnicodeCategory.OtherLetter != category &&
                          UnicodeCategory.LetterNumber != category &&
                          UnicodeCategory.NonSpacingMark != category &&
                          UnicodeCategory.SpacingCombiningMark != category &&
                          UnicodeCategory.DecimalDigitNumber != category &&
                          UnicodeCategory.ConnectorPunctuation != category)
                    {
                        result = result.Replace(result[ordinal], '_');
                    }
                    ordinal++;
                }

                //error if not valid:
                if (!CodeProvider.IsValidIdentifier(result))
                {
                    throw new ApplicationException(string.Format("Invalid identifier: {0}, could not make valid: {1}", idName, result));
                }
            }
            return result;
        }

        public static string GetFieldName(string name)
        {
            name = name.TrimStart("_".ToCharArray());
            name = ToCamelCase(name);
            name = string.Concat("_", name);
            return CodeDomHelper.GetValidId(name);
        }

        public static string ToCamelCase(string identifier)
        {
            return string.Format("{0}{1}", identifier.Substring(0, 1).ToLower(), identifier.Substring(1));
        }

        public static string ToPascalCase(string identifier)
        {
            return string.Format("{0}{1}", identifier.Substring(0, 1).ToUpper(), identifier.Substring(1));
        }

        public static string GetPropertyName(string name)
        {
            return CodeDomHelper.GetValidId(ToPascalCase(name));
        }

        public static CodeMemberMethod GetMethod(string methodName, MemberAttributes attributes)
        {
            return GetMethod(methodName, null, attributes, null);
        }

        public static CodeMemberMethod GetMethod(string methodName, string returnTypeName, MemberAttributes attributes)
        {
            return GetMethod(methodName, returnTypeName, attributes, null);
        }

        public static CodeMemberMethod GetMethod(string methodName, MemberAttributes attributes, Dictionary<string, Type> parameters)
        {
            return GetMethod(methodName, null, attributes, parameters);
        }

        public static CodeMemberMethod GetMethod(string methodName, string returnTypeName, MemberAttributes attributes, Dictionary<string, Type> parameters)
        {
            CodeMemberMethod method = new CodeMemberMethod();
            method.Name = methodName;
            method.Attributes = attributes;
            if (returnTypeName != null)
            {
                method.ReturnType = new CodeTypeReference(returnTypeName);
            }
            if (parameters != null)
            {
                foreach(KeyValuePair<string, Type> parameter in parameters)
                {
                    method.Parameters.Add(new CodeParameterDeclarationExpression(parameter.Value, parameter.Key));
                }
            }
            return method;
        }

        public static CodeTypeDeclaration GetStruct(string name, MemberAttributes attributes)
        {
            CodeTypeDeclaration structDeclaration = new CodeTypeDeclaration(name);
            structDeclaration.IsStruct = true;
            structDeclaration.Attributes = attributes;
            return structDeclaration;
        }

        public static CodeTypeDeclaration GetStructOfConstants<T>(string name, MemberAttributes attributes, Dictionary<string, T> constants)
        {
            CodeTypeDeclaration structDeclaration = GetStruct(name, attributes);
            foreach (KeyValuePair<string, T> constant in constants)
            {
                structDeclaration.Members.Add(GetConstant<T>(constant.Key, constant.Value));
            }
            return structDeclaration;
        }

        public static CodeFieldReferenceExpression GetLocalReference(string fieldName)
        {
            return new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), fieldName);
        }

        public static CodeFieldReferenceExpression GetTypeReference(string typeName, string fieldName)
        {
            return new CodeFieldReferenceExpression(new CodeTypeReferenceExpression(typeName), fieldName);
        }

        public static CodeMethodInvokeExpression GetLocalInvoke(string methodName)
        {
            return new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), methodName);
        }

        public static CodeConditionStatement GetIfNullCondition(CodeFieldReferenceExpression reference)
        {
            CodeConditionStatement ifNull = new CodeConditionStatement();
            CodeBinaryOperatorExpression ifNullCondition = new CodeBinaryOperatorExpression();
            ifNullCondition.Left = reference;
            ifNullCondition.Operator = CodeBinaryOperatorType.ValueEquality;
            ifNullCondition.Right = new CodePrimitiveExpression(null);
            ifNull.Condition = ifNullCondition;
            return ifNull;
        }

        public static CodeMemberProperty GetLazyLoadedProperty(string propertyName, string fieldName, Type propertyType, bool readOnly)
        {
            CodeFieldReferenceExpression fieldReference = CodeDomHelper.GetLocalReference(fieldName);
            CodeConditionStatement ifNull = CodeDomHelper.GetIfNullCondition(fieldReference);
            ifNull.TrueStatements.Add(new CodeAssignStatement(fieldReference, new CodeObjectCreateExpression(propertyType)));
            CodeMemberProperty property = CodeDomHelper.GetProperty(propertyName, propertyType, MemberAttributes.Private, null);
            property.GetStatements.Add(ifNull);
            property.GetStatements.Add(new CodeMethodReturnStatement(fieldReference));
            if (!readOnly)
            {
                property.SetStatements.Add(new CodeAssignStatement(fieldReference, new CodePropertySetValueReferenceExpression()));
            }
            return property;
        }
    }
}
